/**
* AutoSWF - Cody Cushing. March 26, 2009
* Copyright (c) 2009 Cody Cushing
* No use restrictions. Maintain this notice in all versions. 
**/

package com.autoswf.core
{
	import flash.errors.IllegalOperationError;
	import flash.utils.Dictionary;
	
	/**
	 * Core component for bi-directional chain of command messaging. Creates an undirected graph of parents, 
	 * and directed graph of children. References passed up the chain are passed to all parents, references
	 * passed down the chain are given to the node with matching id in path address.  
	 */	
	public class Link implements ILink
	{
		protected var _id:String;
		protected var _parentLinks:Dictionary;
		protected var _childLinks:Dictionary;
		
		protected var _client:Object = this;
		protected var _hook:Function;
		
		
		/**
		 * Constructor
		 *  
		 * @param id  The id of this node. Must be different from sibling id for proper addressing by parent nodes.
		 */
		public function Link(id:String)
		{
			_id = id;
			
			init();
		}
		
		/** Create parent and child dictionaries with weak references, override to create dictionaries with strong references. */
		protected function init():void
		{
			_parentLinks = new Dictionary(true);
			_childLinks = new Dictionary(true);
		}
		
		
		public function get id():String
		{
			return _id;
		}
		
		
		/** @param value  The target to test the payload of the object reference on. Client points to 'this' by default but can be 
		 * repointed to separate objects. */
		public function set client(value:Object):void
		{
			_client = value;
		}
		

		/** @param value  A function to invoke and pass the transport to before passing the transport up the chain. */
		public function set hook(value:Function):void
		{
			_hook = value;
		}


		public function addParentLink(link:ILink):void
		{
			if(_parentLinks.hasOwnProperty(link.id))
			{
				throw new IllegalOperationError("Id collision, adding parent with same id: " + link.id);
			}
			_parentLinks[link.id] = link;
		}
		
		
		public function removeParentLink(link:ILink):void
		{
			delete _parentLinks[link.id];
		}
		
		
		public function addChildLink(link:ILink):void
		{
			if(_childLinks.hasOwnProperty(link.id))
			{
				throw new IllegalOperationError("Id collision, adding child with same id: " + link.id);
			}
			_childLinks[link.id] = link;
		}
		
		
		public function removeChildLink(link:ILink):void
		{
			delete _childLinks[link.id];
		}
		
		
		/**
		 * Method for passing an object to subscribing parent nodes. Object is passed through 'hook' callback if one has 
		 * been defined before being sent up the chain. Override this method to create a custom handler for incoming references. 
		 *  
		 * @param object  An object whose reference will be passed to all parent nodes. 
		 */
		public function sendUp(object:*):void
		{
			/* check that the object is wrapped in a mediator so path information can be preserved. */
			var transport:Transport = (object is Transport) ? object : new Transport(object);

			_hook && _hook(transport); // check if there is a hook callback and invoke it.

			notifyParents(transport);
		}
		
		
		protected function notifyParents(transport:Transport):void
		{
			transport.addPath(_id);
			
			for(var p:String in _parentLinks)
			{ 
				ILink(_parentLinks[p]).sendUp(transport);
			}
		}
		
		
		/**
		 * Method to pass an object (Transport) to a specific node via a path address. If the next node id is not 
		 * a member of this node's child collection, the reference is no longer passed, instead it is checked 
		 * against this link's client's public methods and properties. If a match is found, that method is 
		 * invoked or that property is returned. If the message property of the object contains this nodes id, this
		 * node's reference is returned.
		 * 
		 * @param object  An object, typically a Transport with path information, to be passed down the chain or tested 
		 * on this node.
		 * @return      The result of a function or property test at the final location.
		 */
		public function sendDown(object:*):Object
		{
			var transport:Transport = (object is Transport) ? object : new Transport(object);
			var proposition:String = transport.proposition;

			/* If it's a childLink pass it down, building the return path */
			if(_childLinks.hasOwnProperty(proposition))
			{
				transport.removePath();
				var childLink:ILink = _childLinks[proposition];
				return childLink.sendDown(transport);
			}
			
			/* if it's a public function then invoke it */
 			if(_client.hasOwnProperty(proposition) && _client[proposition] is Function)
			{
				var func:Function = _client[proposition];
				return func.apply(null, transport.arguments);
			}
			
			/* if it's just a property, return its value */
 			if(_client.hasOwnProperty(proposition))
			{
				return _client[proposition];
			}
			
			/* if the actual message value is this node's id, return this node's reference. */
			if(transport.message == _id)
			{
				return this;
			}
			
			return null;
		}
		
		
		public function toString():String
		{
			return _id;
		}
	}
}